#include<iostream>
#include<unistd.h>
#include<pthread.h>
using namespace std;

//学习线程间同步的条件变量以及他们相关的函数接口
int cnt=0;

//学习条件变量是为了学习消费者生产者模型cp问题

//cp问题：也就是生产者消费者问题：
/*
生产者消费者模型（Producer-Consumer Model）是计算机科学中的经典同步问题，涉及到两种类型的进程（或线程）：生产者和消费者。生产者负责生成数据，消费者负责处理（消费）数据。这个模型的关键是如何协调生产者和消费者之间的工作，以确保数据能够高效、安全地在两者之间传递。

核心问题
数据共享：生产者生成的数据需要被消费者处理，因此需要一种机制来共享数据。
同步：需要确保生产者不会在缓冲区满时添加数据，消费者不会在缓冲区空时尝试取数据。
互斥：对共享缓冲区的访问需要互斥，以防止多个生产者或消费者同时访问缓冲区，造成数据不一致。
解决方案
1,互斥锁（Mutex）：用于保护对共享缓冲区的访问，确保任何时候只有一个生产者或消费者可以访问缓冲区。 对应我们的目录blockqueue(阻塞队列)
2,信号量（Semaphore）                                                                      对应我们的目录ringqueue(环形队列)
*/


//条件变量的意义：
//条件变量（condition variable）在多线程编程中的意义在于提供一种机制，允许线程在某个条件未满足时挂起（阻塞）自己的执行，直到另一个线程在条件变量上发出信号，表明条件已经满足。这种机制使得线程同步更加灵活和高效，特别是在涉及多个线程协调工作的场景中。

//全局声明一把锁：
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;

//全局声明一个条件变量：
//pthread_cond_t是用于线程间同步的条件变量类型
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;//PTHREAD_COND_INITIALIZER 初始化一个 pthread_cond_t 变量时，你不需要（也不应该）在程序运行时调用 pthread_cond_init 函数来初始化它。

//pthread_cond_signal 是 POSIX 线程库中的一个函数，用于在多线程程序中唤醒等待特定条件变量的一个线程。如果你有多个线程在条件变量上等待，pthread_cond_signal 只会唤醒其中一个线程,默认是第一个
//pthread_cond_broadcast就是唤醒该条件变量下的所有的正在等待的线程

void *Count(void *args)
{
    pthread_detach(pthread_self());

    uint64_t number=(uint64_t)args;//把指针类型转为uint类型的整数
    cout<<"pthread"<<number<<"begin"<<endl;
    while(true)
    {
        pthread_mutex_lock(&lock);//申请锁
        //我们判断临界资源是否就绪也是在访问临界资源，所以判断临界资源的条件判断必须在加锁之后，就像抢票的例子中，申请锁成功的线程才会去判断此时的票的数量

        //当一个线程持有锁，但是此时临界资源没有准备好的时候，就要让线程前往对应的条件变量下等待
        pthread_cond_wait(&cond,&lock);//参数是对应的条件变量和对应的锁
        //注意：wait的线程会进入该条件变量下的等待队列，并且同时会释放锁资源！！！，等到被唤醒的时候会重新持有锁资源,当多个线程被唤醒的时候，就会重新去竞争锁，竞争失败的一样阻塞着

        cout<<"pthread"<<number<<"cnt:"<<cnt++<<endl;
        pthread_mutex_unlock(&lock);
    }


}

int main()
{
    for(uint64_t i=0;i<5;i++)//由于linux环境是64位环境，所以把i强转为(void *)指针类型时，要保证i是64位的，uint64_t 就是64位的无符号整数
    {
        pthread_t tid;
        pthread_create(&tid,nullptr,Count,(void *)i);
    }
    sleep(3);
    cout<<"main thread begin"<<endl;
    while(true)
    {
        sleep(1);
        pthread_cond_signal(&cond);//唤醒在cond条件下等待队列的一个线程
        //pthread_cond_broadcast(&cond)唤醒条件下所有的线程，此时所有的线程又会去竞争锁，没有竞争到的线程就会在锁上等待
        cout<<"signal a thread"<<endl;
    }
    return 0;
}

//一个小的注意点：关于cp问题的代码在运行时，打印总会时不时出现一些顺序错乱的情况，比如先打印处理任务才打印生产了这个任务，这并不是代逻辑错误：
//解释：顺序错乱的原因主要是由于多个线程同时访问和操作共享资源（在这种情况下是标准输出流 cout）而没有适当的同步措施。这属于并发编程中典型的同步问题，并不是代码逻辑错误，而是由于输入输出（I/O）操作在多线程环境下的线程安全问题。
/*原理解析：
一，为什么会出现顺序错乱？
1，共享资源竞争：
当多个线程尝试同时向标准输出流写入数据时，由于 cout 是一个共享资源，没有互斥保护，所以输出可能会被中断或重叠，导致输出内容的顺序与预期不一致。

2，缓冲区行为：
标准输出流 cout 是带缓冲的。这意味着输出可能不会立即显示在控制台上，而是先存储在缓冲区中。不同线程对缓冲区的并发访问可能导致输出内容的交错。

3，线程调度：
线程的调度是由操作系统管理的，线程执行的顺序和时间是不确定的。因此，即使程序逻辑正确，线程的执行顺序也可能导致输出顺序错乱。

二，如何解决顺序错乱的问题？
1，使用互斥锁：,
可以通过在输出操作前后加锁来确保每次只有一个线程可以向 cout 写入数据。这可以通过 mutex 实现，如前所述。

2，使用条件变量：
如果需要保持输出的特定顺序，可以使用条件变量来控制线程执行的顺序。

3，使用线程安全的输出流：
C++11 标准库提供了线程安全的输出流，例如 std::ostringstream，可以用来避免多线程输出时的冲突。

4，减少共享资源的访问：
尽量减少多个线程对共享资源（如 cout）的直接访问，或者确保每次只有一个线程可以访问共享资源。
三，结论：
顺序错乱的问题主要是由于多线程环境下对共享资源（标准输出流）的并发访问没有适当的同步措施导致的。这不是代码逻辑错误，而是并发编程中的一个常见问题。通过引入适当的同步机制，可以解决这个问题，确保输出的顺序和一致性。
*/
